package com.assess.algorithm;

import java.util.Objects;

/**
 * 通用 AHP 工具（支持任意维度与任意指标集合，适用于指标树的任意节点）。
 *
 * 说明：
 * - 输入为 n×n 成对比较矩阵（正互反矩阵），输出对应 n 个子指标的局部权重及一致性度量。
 * - 可用于“指标树”的每个非叶子节点，对其直接子节点计算局部权重。
 */
public final class MedicalAHP {

    private MedicalAHP() {}

    /**
     * 使用几何平均法，从 n×n 成对比较矩阵计算归一化权重（需为正互反矩阵）。
     */
    public static double[] computeWeights(double[][] pairwiseMatrix) {
        validateSquareMatrix(pairwiseMatrix);
        final int n = pairwiseMatrix.length;

        double[] geometricMeans = new double[n];
        for (int i = 0; i < n; i++) {
            double product = 1.0;
            for (int j = 0; j < n; j++) {
                double v = pairwiseMatrix[i][j];
                if (v <= 0) throw new IllegalArgumentException("矩阵元素必须大于 0");
                product *= v;
            }
            geometricMeans[i] = Math.pow(product, 1.0 / n);
        }

        return normalize(geometricMeans);
    }

    /** 通过 Aw/wi 的平均法近似计算最大特征值（lambda_max）。 */
    public static double computeLambdaMax(double[][] pairwiseMatrix, double[] weights) {
        validateSquareMatrix(pairwiseMatrix);
        Objects.requireNonNull(weights, "weights");
        final int n = pairwiseMatrix.length;
        if (weights.length != n) throw new IllegalArgumentException("权重长度与矩阵阶数不一致");

        double[] Aw = new double[n];
        for (int i = 0; i < n; i++) {
            double sum = 0.0;
            for (int j = 0; j < n; j++) sum += pairwiseMatrix[i][j] * weights[j];
            Aw[i] = sum;
        }

        double lambdaSum = 0.0;
        for (int i = 0; i < n; i++) {
            if (weights[i] == 0) throw new IllegalArgumentException("权重中包含 0");
            lambdaSum += Aw[i] / weights[i];
        }
        return lambdaSum / n;
    }

    /** 一致性指标（CI） */
    public static double computeCI(double lambdaMax, int n) {
        if (n <= 1) return 0.0;
        return (lambdaMax - n) / (n - 1);
    }

    /** 一致性比例（CR） = CI / RI(n)。 */
    public static double computeCR(double ci, int n) {
        double ri = randomIndex(n);
        if (ri == 0.0) return 0.0;
        return ci / ri;
    }

    /** 便捷方法：一次性计算权重与一致性结果。 */
    public static Result compute(double[][] pairwiseMatrix) {
        double[] w = computeWeights(pairwiseMatrix);
        int n = pairwiseMatrix.length;
        double lambda = computeLambdaMax(pairwiseMatrix, w);
        double ci = computeCI(lambda, n);
        double cr = computeCR(ci, n);
        return new Result(w, lambda, ci, cr);
    }

    /** Saaty 随机一致性指数（n=1..15）。 */
    public static double randomIndex(int n) {
        switch (n) {
            case 0:
            case 1:
            case 2: return 0.0;
            case 3: return 0.58;
            case 4: return 0.90;
            case 5: return 1.12;
            case 6: return 1.24;
            case 7: return 1.32;
            case 8: return 1.41;
            case 9: return 1.45;
            case 10: return 1.49;
            case 11: return 1.51;
            case 12: return 1.48;
            case 13: return 1.56;
            case 14: return 1.57;
            case 15: return 1.59;
            default: return 1.98 * (n - 2) / n; // n>15 的近似
        }
    }

    private static double[] normalize(double[] v) {
        double sum = 0.0;
        for (double x : v) {
            if (x < 0) throw new IllegalArgumentException("向量中存在负数");
            sum += x;
        }
        if (sum == 0.0) throw new IllegalArgumentException("元素之和不能为 0");
        double[] out = new double[v.length];
        for (int i = 0; i < v.length; i++) out[i] = v[i] / sum;
        return out;
    }

    private static void validateSquareMatrix(double[][] m) {
        Objects.requireNonNull(m, "matrix");
        if (m.length == 0) throw new IllegalArgumentException("empty matrix");
        int n = m.length;
        for (int i = 0; i < n; i++) {
            if (m[i] == null || m[i].length != n)
                throw new IllegalArgumentException("矩阵必须为 n×n 的方阵");
        }
    }

    public static final class Result {
        private final double[] weights;
        private final double lambdaMax;
        private final double ci;
        private final double cr;

        public Result(double[] weights, double lambdaMax, double ci, double cr) {
            this.weights = weights; this.lambdaMax = lambdaMax; this.ci = ci; this.cr = cr;
        }
        public double[] getWeights() { return weights; }
        public double getLambdaMax() { return lambdaMax; }
        public double getCi() { return ci; }
        public double getCr() { return cr; }
    }
}


